package com.site.blog.aopconf;

import cn.hutool.json.JSONUtil;
import com.site.blog.annotation.MyLog;
import com.site.blog.entity.log.WebLog;
import com.site.blog.service.LogService;
import com.site.blog.util.MTUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author : Connor-G 康纳-郭
 * @date : 18:13 20.12.20
 * @Company : http://www.zzrgsoft.org
 * @Version : 1.0
 */

@Aspect //开启apo切面
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private LogService logService;

    @Autowired
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 配置 切入点表达式
     * 1. @Pointcut("execution(public * com.hyh.web.*.*(..))")  web层的所有方法
     * 2. @Pointcut("@annotation(com.hyh.annotation.MyLog)")      MyLog注解标注的方法
     */
    @Pointcut("@annotation(com.site.blog.annotation.MyLog)")
    public void webLogPoint() {
    }


    /**
     * 环绕通知
     */
    @Around("webLogPoint()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取请求对象
        HttpServletRequest request = getRequest();

        // 日志记录对象
        WebLog webLog = new WebLog();
        Object result = null;
        try {
            webLog.setGmtCreate(new Date());
            log.info("=============前置通知=============");
            // 开始时间
            long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            log.info("=============返回通知=============");
            // 计算 时间差
            long timeCost = System.currentTimeMillis() - start;
            // 获取自定义注解
            MyLog annotation = getAnnotation(joinPoint);
            // 封装记录日志对象
            webLog.setMethodType(annotation.methodType().getMethodCode());
            webLog.setDescription(annotation.description());
            webLog.setTimeCost(timeCost);
            webLog.setStartTime(start);
            // 获取客户端ip
            String getRemoteAddr = MTUtils.getip(request);
            webLog.setIpAddress(getRemoteAddr);

            webLog.setHttpMethod(request.getMethod());

            //webLog.setParams(JSONUtil.parse(getParams(joinPoint)).toString());
            // 方法返回值太大、暂时不保存
            /*Map<String, Object> mapResult = new HashMap<>();
            mapResult.put("result", result);
            String s = JSONUtil.parse(mapResult).toStringPretty();
            webLog.setResult(s);*/

            webLog.setUri(request.getRequestURI());
            webLog.setUrl(request.getRequestURL().toString());
            log.info("{}", JSONUtil.parse(webLog));

            // 异步保存日志
            threadPoolTaskExecutor.execute(new SaveLogThread(webLog, logService));
        } catch (Exception e) {
            log.info("=============异常通知=============");
            log.error(e.getMessage());
            throw new Throwable(e);
        } finally {
            log.info("=============后置通知=============");
        }
        return result;
    }

    /**
     * 获取方法上的注解
     */
    private MyLog getAnnotation(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        return method.getAnnotation(MyLog.class);
    }

    /**
     * 获取参数 params:{"name":"天乔巴夏"}
     */
    private Object getParams(ProceedingJoinPoint joinPoint) {
        // 参数名
        String[] paramNames = getMethodSignature(joinPoint).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();
        // 存储参数
        Map<String, Object> params = new LinkedHashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            // MultipartFile对象以文件名作为参数值
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();
            }
            params.put(paramNames[i], value);
        }
        return params;
    }

    /**
     * 获取 方法类型
     */
    private MethodSignature getMethodSignature(ProceedingJoinPoint joinPoint) {
        return (MethodSignature) joinPoint.getSignature();
    }

    /**
     * 获取 Request请求对象
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }


    /**
     * 保存日志线程
     */
    private static class SaveLogThread implements Runnable {
        private WebLog log;
        private LogService logService;

        public SaveLogThread(WebLog log, LogService logService) {
            this.log = log;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.addLog(log);
        }
    }
}
